package org.seedltd.upms.service.impl.detection;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.seedltd.base.exception.BusinessException;
import org.seedltd.core.base.BasePage;
import org.seedltd.core.base.BaseServiceImpl;
import org.seedltd.core.mybatis.conditions.ProQuery;
import org.seedltd.core.utils.*;
import org.seedltd.oss.service.IOssService;
import org.seedltd.springsecurity.SeedltdUserDetails;
import org.seedltd.springsecurity.utils.UserUtils;
import org.seedltd.upms.constant.FolderBucket;
import org.seedltd.upms.constant.GlobalConst;
import org.seedltd.upms.enums.OrderStatus;
import org.seedltd.upms.mapper.detection.DetectionTubeMapper;
import org.seedltd.upms.model.detection.entity.DetectionOrder;
import org.seedltd.upms.model.detection.entity.DetectionQrcode;
import org.seedltd.upms.model.detection.entity.DetectionTube;
import org.seedltd.upms.model.detection.form.DetectionCheckTemplateForm;
import org.seedltd.upms.model.detection.form.DetectionOrderProductForm;
import org.seedltd.upms.model.detection.form.DetectionTubeForm;
import org.seedltd.upms.model.detection.form.DetectionTubeSearchForm;
import org.seedltd.upms.model.detection.vo.*;
import org.seedltd.upms.model.sys.entity.SysUser;
import org.seedltd.upms.model.sys.vo.SysAttachmentVO;
import org.seedltd.upms.service.detection.*;
import org.seedltd.upms.service.sys.SysAttachmentService;
import org.seedltd.upms.service.sys.SysUserService;
import org.seedltd.wxmp.core.WeCharMiniAppService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import com.google.common.collect.Lists;

import cn.binarywang.wx.miniapp.bean.WxMaSubscribeMessage;
import cn.binarywang.wx.miniapp.constant.WxMaConstants;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ZipUtil;
import me.chanjar.weixin.common.error.WxErrorException;

/**
 * 试剂管维护业务实现
 * 
 * @author laizuan
 * @since 2021/07/31
 */
@Service
public class DetectionTubeServiceImpl extends BaseServiceImpl<DetectionTubeMapper, DetectionTube>
    implements DetectionTubeService {
    private final DetectionQrcodeService detectionQrcodeService;
    private final DetectionCheckTemplateService detectionCheckTemplateService;
    private final DetectionOrderLogService detectionOrderLogService;
    private final SysAttachmentService sysAttachmentService;
    private final DetectionOrderService detectionOrderService;
    private final WeCharMiniAppService weCharMiniAppService;
    private final DetectionOrderTracingService detectionOrderTracingService;
    private final SysUserService sysUserService;
    private final DetectionOrderBindingService detectionOrderBindingService;
    private final DetectionProductItemService detectionProductItemService;
    private final IOssService ossService;

    @Value("${spring.profiles.active}")
    private String dev;

    @Value("${seedltd.wx.miniapp.templateId}")
    private String templateKey;

    public DetectionTubeServiceImpl(@Lazy DetectionQrcodeService detectionQrcodeService,
        DetectionCheckTemplateService detectionCheckTemplateService, DetectionOrderLogService detectionOrderLogService,
        SysAttachmentService sysAttachmentService, @Lazy DetectionOrderService detectionOrderService,
        WeCharMiniAppService weCharMiniAppService, DetectionOrderTracingService detectionOrderTracingService,
        SysUserService sysUserService, DetectionOrderBindingService detectionOrderBindingService,
        DetectionProductItemService detectionProductItemService, IOssService ossService) {
        this.detectionQrcodeService = detectionQrcodeService;
        this.detectionCheckTemplateService = detectionCheckTemplateService;
        this.detectionOrderLogService = detectionOrderLogService;
        this.sysAttachmentService = sysAttachmentService;
        this.detectionOrderService = detectionOrderService;
        this.weCharMiniAppService = weCharMiniAppService;
        this.detectionOrderTracingService = detectionOrderTracingService;
        this.sysUserService = sysUserService;
        this.detectionOrderBindingService = detectionOrderBindingService;
        this.detectionProductItemService = detectionProductItemService;
        this.ossService = ossService;
    }

    @Override
    public DetectionTube findByQrCodeId(Long codeId) {
        return ProQuery.Builder(baseMapper).build().eq(DetectionTube::getQrcodeId, codeId).selectOne();
    }

    @Override
    public DetectionTubeVO findById(Long id) {
        DetectionTube pojo = baseMapper.selectById(id);
        if (pojo == null) {
            return null;
        }
        DetectionTubeVO resultVo = new DetectionTubeVO();
        BeanUtils.copyProperties(pojo, resultVo);

        DetectionQrcodeVO qrcodeVO = detectionQrcodeService.findById(resultVo.getQrcodeId());
        resultVo.setCodeNumber(qrcodeVO.getCodeNumber());

        List<SysAttachmentVO> sysAttachments = sysAttachmentService.listByLinkedId(id);
        resultVo.setAttachmentList(sysAttachments);

        DetectionOrder detectionOrder = detectionOrderService.getByTubeId(id);
        if (detectionOrder == null) {
            return resultVo;
        }
        List<DetectionProductItemVO> list = detectionProductItemService.listAll2();
        Map<String, DetectionProductItemVO> maps =
            list.stream().collect(Collectors.toMap(DetectionProductItemVO::getItemName, (f) -> f));
        List<DetectionOrderProductVO> products = detectionOrderService.listProductByOrderId(detectionOrder.getId());
        for (DetectionOrderProductVO product : products) {
            String itemNameVal = product.getItemNameVal();
            String[] items = StringUtils.split(itemNameVal, ",");
            List<DetectionOrderProductVO.ProductItem> productItemList = Lists.newArrayList();
            for (String item : items) {
                String[] itemVal = StringUtils.split(item, ":");
                DetectionOrderProductVO.ProductItem productItem = new DetectionOrderProductVO.ProductItem();
                if (itemVal.length == 2) {
                    productItem.setName(itemVal[0]);
                    productItem.setValue(itemVal[1]);
                    productItem.setDetectionProductItem(maps.get(productItem.getName()));
                    productItemList.add(productItem);
                } else if (itemVal.length == 1) {
                    productItem.setName(itemVal[0]);
                    productItem.setDetectionProductItem(maps.get(productItem.getName()));
                    productItemList.add(productItem);
                }
            }
            product.setProductItemList(productItemList);
            product.setItemNameVal(null);
        }
        resultVo.setDetectionOrderProductList(products);
        return resultVo;
    }

    @Override
    @Transactional
    public Long add(DetectionTubeForm form) {
        String codeNo = form.getCodeNumber();
        DetectionQrcode detectionQrcode = detectionQrcodeService.findByCodeNo(codeNo, true);
        this.checkQrCodeStatus(codeNo, detectionQrcode.getState());
        final Long codeId = detectionQrcode.getId();
        DetectionTube pojo = new DetectionTube();
        pojo.setRemark(form.getRemark());
        pojo.setQrcodeId(codeId);
        pojo.setCheckState(GlobalConst.CHECK_STATE_UNSTART);
        pojo.setUploadState(GlobalConst.NO);
        baseMapper.insert(pojo);
        this.bindingQrCode(codeId);
        return pojo.getId();
    }

    private void bindingQrCode(Long codeId) {
        DetectionQrcode updateQr = new DetectionQrcode();
        updateQr.setId(codeId);
        updateQr.setState(GlobalConst.QRCODE_STATE_BINDING);
        detectionQrcodeService.updateById(updateQr);
    }

    @Override
    @Transactional
    public Long update(DetectionTubeForm form, MultipartFile[] file) throws IOException {
        String codeNo = form.getCodeNumber();
        DetectionQrcode detectionQrcode = detectionQrcodeService.findByCodeNo(codeNo, false);
        if (GlobalConst.QRCODE_STATE_INVALID.equals(detectionQrcode.getState())) {
            throw new BusinessException(String.format("二维码【%s】已失效，不能操作试剂管", codeNo));
        }

        DetectionTube pojo = baseMapper.selectById(form.getId());
        Assert.notNull(pojo, "未找到需要修改的数据");

        if (GlobalConst.CHECK_STATE_FINISH.equals(pojo.getCheckState())) {
            throw new BusinessException("订单已完结，不能操作。");
        }

        pojo.setRemark(form.getRemark());
        pojo.setCheckResult(form.getCheckResult());
        pojo.setResultScope(form.getResultScope());
        pojo.setDisturbResultScope(form.getDisturbResultScope());
        pojo.setDisturbResultState(form.getDisturbResultState());
        pojo.setCheckState(GlobalConst.CHECK_STATE_UNSTART);

        if (!pojo.getQrcodeId().equals(detectionQrcode.getId())) {
            final Long qrcodeId = detectionQrcode.getId();
            Integer state = detectionQrcode.getState();
            this.checkQrCodeStatus(codeNo, state);
            this.bindingQrCode(qrcodeId);
            pojo.setQrcodeId(qrcodeId);
        }

        // 保存模板
        if (GlobalConst.YES.equals(form.getSaveTemplate()) && StringUtils.isNotBlank(form.getCheckResult())) {
            if (!detectionCheckTemplateService.checkExists(form.getCheckResult())) {
                DetectionCheckTemplateForm detectionCheckTemplateForm = new DetectionCheckTemplateForm();
                detectionCheckTemplateForm.setTag(form.getTag());
                detectionCheckTemplateForm.setContent(form.getCheckResult());
                detectionCheckTemplateService.add(detectionCheckTemplateForm);
            }
        }

        sysAttachmentService.addBatch(file, pojo.getId(), FolderBucket.REPORT.getBucketName());
        if (file != null) {
            pojo.setUploadState(GlobalConst.YES);
        }

        String detectionOrderProductList = form.getDetectionOrderProductList();

        DetectionOrder detectionOrder = null;
        if (StringUtils.isNotBlank(detectionOrderProductList)) {
            // 保存检测项信息
            detectionOrder = detectionOrderService.getByTubeId(pojo.getId());
            if (detectionOrder != null) {
                try {
                    List<DetectionOrderProductForm> detectionOrderProductForms =
                        JsonUtils.toBeans(detectionOrderProductList, DetectionOrderProductForm.class);

                    for (DetectionOrderProductForm detectionOrderProductForm : detectionOrderProductForms) {
                        String itemStates = detectionOrderProductForm.getProductItemList().stream()
                            .map(item -> item.getName() + ":" + item.getValue()).collect(Collectors.joining(","));
                        String state = detectionOrderProductForm.getState();
                        detectionOrderService.updateOrderProduct(detectionOrderProductForm.getOrderId(),
                            detectionOrderProductForm.getProductId(), state, itemStates);
                    }
                } catch (Exception e) {
                    throw new BusinessException("检测项目结果转换异常", e);
                }
            }
        }

        // 检查完成
        if (GlobalConst.CHECK_STATE_FINISH.equals(form.getCheckState())) {
            if (baseMapper.checkOrderExists(pojo.getQrcodeId(), pojo.getId()) == null) {
                throw new BusinessException("用户未下订单不能设置订单状态和检测结果");
            }

            if (StringUtils.isBlank(detectionOrderProductList)) {
                throw new BusinessException("产品检测项目结果不能为空");
            }

            Assert.notNull(detectionOrder, "试剂管未绑定到订单信息，不能完成检测操作。");

            final Long orderId = detectionOrder.getId();

            pojo.setCheckState(GlobalConst.CHECK_STATE_FINISH);
            SeedltdUserDetails loginUser = UserUtils.getLoginUser();

            // 更新订单状态
            DetectionOrder update = new DetectionOrder();
            update.setId(orderId);
            update.setState(OrderStatus.DONE.getValue());
            update.setCheckState(GlobalConst.YES);
            detectionOrderService.updateById(update);
            detectionOrderLogService.add(loginUser.toString(), new Date(), orderId, "将订单状态修改成检测完成");
            detectionOrderTracingService.updateTracing(orderId, OrderStatus.DONE.getValue(), loginUser.getNickName(),
                new Date(), false, null);

            DetectionOrderBindingVO orderBinding = detectionOrderBindingService.findByOrderId(orderId);

            List<SysAttachmentVO> attachmentList = sysAttachmentService.listByLinkedId(pojo.getId());
            if (CollectionUtils.isEmpty(attachmentList)) {
                throw new BusinessException("请上传检测报告");
            } else if (attachmentList.size() > 1) {
                // 多个PDF合并成一个
                List<byte[]> byteList = Lists.newArrayList();
                for (SysAttachmentVO attachment : attachmentList) {
                    FolderBucket bucket = FolderBucket.getByBuckName(attachment.getBucket());
                    byte[] bytes;
                    try {
                        bytes = ossService.download(bucket, attachment.getPath());
                    } catch (IOException e) {
                        throw new BusinessException("上传文件异常，请删除文件后重新上传");
                    }
                    byteList.add(bytes);
                }
                File newFile = new File(FileUtils.getTempDirectoryPath() + "/report/" + StringUtils.uuid() + ".pdf");
                try {
                    PdfUtils.mergePdfFiles(byteList, new FileOutputStream(newFile));
                    sysAttachmentService.delete(null, pojo.getId(), false);
                    sysAttachmentService.add(newFile, pojo.getId(), FolderBucket.REPORT.getBucketName());
                } catch (Exception e) {
                    logger.error(e.getMessage(), e);
                    throw new BusinessException("合并PDF异常，请检查您上传的PDF是否有效或者是否PDF经过加密处理");
                } finally {
                    FileUtils.deleteQuietly(newFile);
                }
            }
            // 通知客人
            sendMsgToUser(orderBinding, detectionOrder, form.getDisturbResultState());
        }
        baseMapper.updateById(pojo);
        return pojo.getId();
    }

    /**
     * 发送消息小程序消息给用户
     * 
     */
    private void sendMsgToUser(DetectionOrderBindingVO binding, DetectionOrder order, String state) {
        SysUser sysUser = sysUserService.findById(order.getUserId());
        String openId = Optional.ofNullable(sysUser).map(SysUser::getWxOpenId).orElse(null);
        if (StringUtils.isEmpty(openId)) {
            return;
        }
        WxMaSubscribeMessage subscribeMessage = new WxMaSubscribeMessage();
        subscribeMessage.setToUser(openId);
        subscribeMessage.setTemplateId(templateKey);
        subscribeMessage.setPage(String.format("pages/home/index?type=%s&orderId=%s", "card", order.getId()));

        // 采样者
        WxMaSubscribeMessage.MsgData data1 = new WxMaSubscribeMessage.MsgData();
        data1.setName("thing17");
        data1.setValue(binding.getUsername());

        // 检测时间
        WxMaSubscribeMessage.MsgData data2 = new WxMaSubscribeMessage.MsgData();
        data2.setName("time16");
        data2.setValue(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm"));

        // 服务项目
        WxMaSubscribeMessage.MsgData data3 = new WxMaSubscribeMessage.MsgData();
        data3.setName("thing1");
        data3.setValue(order.getProductNames());

        // 检测结果
        WxMaSubscribeMessage.MsgData data4 = new WxMaSubscribeMessage.MsgData();
        data4.setName("phrase10");
        data4.setValue(state);

        List<WxMaSubscribeMessage.MsgData> dataList = Lists.newArrayList(data1, data2, data3, data4);
        subscribeMessage.setData(dataList);
        subscribeMessage.setMiniprogramState(
            isDev() ? WxMaConstants.MiniProgramState.TRIAL : WxMaConstants.MiniProgramState.FORMAL);
        try {
            weCharMiniAppService.switchoverToDefault().getMsgService().sendSubscribeMsg(subscribeMessage);
        } catch (WxErrorException e) {
            logger.error("发送小程序消息异常 {}", e.getError().toString(), e);
        }
    }

    private boolean isDev() {
        return "dev".equals(dev);
    }

    private void checkQrCodeStatus(String codeNo, Integer state) {
        if (GlobalConst.QRCODE_STATE_BINDING.equals(state)) {
            throw new BusinessException(String.format("二维码【%s】已绑定", codeNo));
        }
        if (GlobalConst.QRCODE_STATE_INVALID.equals(state)) {
            throw new BusinessException(String.format("二维码【%s】已失效", codeNo));
        }
    }

    @Override
    public BasePage<DetectionTubeVO> page(DetectionTubeSearchForm searchForm) {
        return baseMapper.list(BasePage.of(searchForm.getStart(), searchForm.getSize()), searchForm);
    }

    @Override
    public String uploadReportBatch(MultipartFile file) throws IOException {
        File tempFile = new File(FileUtil.getTmpDirPath() + "/report");
        File unzip = ZipUtil.unzip(file.getInputStream(), tempFile, Charset.forName("GBK"));
        StringBuilder sb;
        try {
            List<File> files = FileUtil.loopFiles(unzip);
            sb = new StringBuilder();
            int countFail = 0;
            for (File f : files) {
                String fileName = f.getName();
                String qrCodeNo = StringUtils.replace(fileName, ".pdf", "");
                DetectionTube detectionTube = baseMapper.getByQrcode(qrCodeNo);
                if (detectionTube == null || GlobalConst.CHECK_STATE_FINISH.equals(detectionTube.getCheckState())) {
                    sb.append(++countFail).append(". 文件名称【").append(fileName).append("】未找到订单数据，或者当前订单状态已经完成。</br>");
                    continue;
                }

                try {
                    detectionTube.setCheckState(null);
                    detectionTube.setUploadState(GlobalConst.YES);
                    this.updateById(detectionTube);
                    sysAttachmentService.add(f, detectionTube.getId(), FolderBucket.REPORT.getBucketName());
                } catch (IOException e) {
                    sb.append(++countFail).append(". 文件名称【").append(fileName).append("】更新或上传失败。</br>");
                }
            }
            if (countFail > 0 && countFail == files.size()) {
                return "全部上传失败：</br>" + sb.toString();
            } else if (countFail > 0 && countFail < files.size()) {
                return String.format("上传成功：%s 份，失败：%s 份。失败内容如下</br>%s", files.size() - countFail, countFail,
                    sb.toString());
            }
        } finally {
            FileUtil.clean(tempFile);
        }
        return sb.toString();
    }
}